
#include <apr_general.h>
#include <apr_getopt.h>
#include <apr_strings.h>
#include "aprtools/APRAutoMemoryPool.h"

#include "utils/Hash.h"
#include "db/Actor.h"
#include "db/KeyDB.h"
#include "net/TCPClient.h"

#include "utils/BaseConvert.h"
#include "utils/StringUtils.h"
#include "utils/StreamFromDirectory.h"
#include "utils/StreamToDirectory.h"
#include "resolver/RemoteResolver.h"

#include "net/Broker5Client.h"

#define STANDARD_BLOCK 1048576

#include "acquire_client.h"
#include "acquire_common.h"
#include "acquire_common_private.h"
#include "acquire_config.h"

/** Register the client of the user with the broker.
 * 
 * @return A code showing the state of the registration with the
 * broker. One is valid success, zero is failure. */
int acquireClientRegisterUserWithBroker()
{
   if (acquireCommonStateIsIncomplete()) return 0;
   APRAutoMemoryPool pool((state.apr_iface)->getGlobalPool());
   char *enckeyfingerprint = (state.keydb)->encryptionTable()->getOwnKey()->getSHA256KeyFingerprint();
   errprint("Registering key fingerprint %s as your encrypting key...", enckeyfingerprint);
   free(enckeyfingerprint);
   char *sgnkeyfingerprint = (state.keydb)->signingTable()->getOwnKey()->getSHA256KeyFingerprint();
   errprint("Registering key fingerprint %s as your signing key...", sgnkeyfingerprint);
   free(sgnkeyfingerprint);
   BrokerMessage bmsg(pool.mp());
   bmsg.setRecipient(state.operational_recipientkeyname);
   bmsg.setSender(state.operational_senderkeyname);
   bmsg.addTypeHeaderNOCRLF("REGISTER_USER_KEY", state.my_name);
   bmsg.insertAvailableKeysToSend(0, state.my_name, NULL, state.keydb);
   // using the WIP password from any table should be fine, as the same
   // password should be used for all tables on the client side.
   char *ssh_username = StringUtils::varprintf("%s_ssh", state.my_name);
   bmsg.insertSSHKeysToSend(1, ssh_username, (state.keydb)->encryptionTable()->getWIPPassword(), state.keydb);
   bmsg.forceNoSignature();
   if (exchangeMessage(bmsg, pool.mp(), NULL) == 1)
   {
      errprint("message exchange failed");
      return 0;
   }
   int retval = -1;
   if (bmsg.isProcessee("AUTHORISED")) retval = ACQUIRE_USER_REGISTER__USER_IS_AUTHORISED;
   if (bmsg.isProcessee("ACCEPTED")) retval = ACQUIRE_USER_REGISTER__IS_ACCEPTED_FOR_REVIEW;
   if (bmsg.isProcessee("INVALID_NAME")) retval = ACQUIRE_USER_REGISTER__NAME_IS_INVALID;
   if (bmsg.isProcessee("UNDER_REVIEW")) retval = ACQUIRE_USER_REGISTER__USER_PENDING_REVIEW;
   if (bmsg.isProcessee("DENIED")) retval = 0;
   return retval;
}

/** Initialise the 'user' client state.
 *
 * @param hostname The host name of the broker server.
 *
 * @param portno The TCP port number of the broker server.
 *
 * @param sendingkeyname The key (actor) to use for sending data.
 *
 * @param my_name The name of this actor.
 *
 * @param password The password required for opening the key chain.
 *
 * @param brokername The actor name of the broker.
 *
 * @return A registration state value: zero on error or failure,
 * non-zero denotes a code denoting a registration status. */
int acquireClientInitState(const char *hostname, int portno, const char *sendingkeyname,
   const char *my_name, const char *password, const char *brokername)
{
   apr_status_t status;
   status = apr_pool_create(&(state.mp), (state.apr_iface)->getGlobalPool());
   CheckAPRError(status);
   if (state.mp == NULL)
   {
      errprint("memory pool for results not created");
      return 0;
   }
   state.portno = portno;
   const char *keyupdatefiles[] = { ACQUIRE_DYNAMIC_DIR "/" "broker.enc",
                                    ACQUIRE_DYNAMIC_DIR "/" "broker.chk",
                                    NULL };
   KeyDB::performExternalKeyUpdates(ACQUIRE_DYNAMIC_DIR "/" "client_keydb", password, keyupdatefiles, state.mp);
   state.keydb = new KeyDB(ACQUIRE_DYNAMIC_DIR "/" "client_keydb", password, my_name, password, 1);
   bool hasOwnEncryption = (state.keydb)->encryptionTable()->hasOwnKey();
   if (!hasOwnEncryption) errprint("missing encryption key for self");
   bool hasOwnDecryption = (state.keydb)->decryptionTable()->hasOwnKey();
   if (!hasOwnDecryption) errprint("missing decryption key for self");
   bool hasOwnSigning = (state.keydb)->signingTable()->hasOwnKey();
   if (!hasOwnSigning) errprint("missing signing key for self");
   bool hasOwnChecking = (state.keydb)->checkingTable()->hasOwnKey();
   if (!hasOwnChecking) errprint("missing checking key for self");
   if ((!hasOwnEncryption) || (!hasOwnDecryption) || (!hasOwnSigning) || (!hasOwnChecking))
   {
      errprint("could not find/generate own ('%s') keys", my_name);
      delete state.keydb;
      state.keydb = NULL;
      return 0;
   }
   (state.keydb)->updateOwnerKeysToLatest(password);
   // show key fingerprints for security?
   char *tmp_oper_brokerkeyname = (state.keydb)->encryptionTable()->getLatestKeyNameOfOwner(brokername);
   if ((tmp_oper_brokerkeyname == NULL) || (strlen(tmp_oper_brokerkeyname) == 0))
   {
      errprint("could not find any public keys owned by the broker");
      delete state.keydb;
      state.keydb = NULL;
      if (tmp_oper_brokerkeyname) free(tmp_oper_brokerkeyname);
      return 0;
   }
   
   EncryptionKey *outbound_enckey = Key::getEncryptionKey(state.keydb, NULL, tmp_oper_brokerkeyname);
   char *enckeyfingerprint = ((AbstractKey *)outbound_enckey)->getSHA256KeyFingerprint();
   errprint("Outbound '%s' encrypting key fingerprint %s...", tmp_oper_brokerkeyname, enckeyfingerprint);
   free(enckeyfingerprint);
   CheckingKey *outbound_chkkey = Key::getCheckingKey(state.keydb, NULL, tmp_oper_brokerkeyname);
   char *chkkeyfingerprint = ((AbstractKey *)outbound_chkkey)->getSHA256KeyFingerprint();
   errprint("Outbound '%s' checking key fingerprint %s...", tmp_oper_brokerkeyname, chkkeyfingerprint);
   free(chkkeyfingerprint);
   char *ssh_username = StringUtils::varprintf("%s_ssh", my_name);
   if (!((state.keydb)->signingTable()->testKeyExists(ssh_username)))
      (state.keydb)->generateSgnChkPairIntoDB(true, false, true, ssh_username, ssh_username);
   state.operational_recipientkeyname = tmp_oper_brokerkeyname;

   if ((sendingkeyname == NULL) || (strlen(sendingkeyname) == 0))
   {
      errprint("sending key name cannot be %s", (sendingkeyname) ? "left blank" : "NULL");
      return 0;
   }
   char *tmp_sendingkeyname = (char *)malloc(sizeof(char)*(strlen(sendingkeyname)+1));
   strcpy(tmp_sendingkeyname, sendingkeyname);
   state.operational_senderkeyname = tmp_sendingkeyname;
   if ((hostname == NULL) || (strlen(hostname) == 0))
   {
      errprint("cannot connect to %s hostname", (hostname) ? "an empty" : "a NULL");
      return 0;
   }
   char *tmp_hostname = (char *)malloc(sizeof(char)*(strlen(hostname)+1));
   strcpy(tmp_hostname, hostname);
   state.hostname = tmp_hostname;
   if ((my_name == NULL) || (strlen(my_name) == 0))
   {
      errprint("username cannot be %s", (my_name) ? "left blank" : "NULL"); 
      return 0;
   }
   char *tmp_username = (char *)malloc(sizeof(char)*(strlen(my_name)+1));
   strcpy(tmp_username, my_name);
   state.my_name = tmp_username;
   if ((password == NULL) || (strlen(password) == 0))
   {
      errprint("password cannot be %s", (password) ? "left blank" : "NULL");
      return 0;
   }
   char *tmp_password = (char *)malloc(sizeof(char)*(strlen(password)+1));
   strcpy(tmp_password, password);
   state.password = tmp_password;
   int regis_t = acquireClientRegisterUserWithBroker();
   switch (regis_t)
   {
      case -1: // broken connection / unknown response
      case 0: // access denied
      case ACQUIRE_USER_REGISTER__NAME_IS_INVALID:
         state.registration_state = ACQUIRE_USER_REGISTRATION_STATE__INVALID;
         break;
      case ACQUIRE_USER_REGISTER__USER_PENDING_REVIEW:
      case ACQUIRE_USER_REGISTER__IS_ACCEPTED_FOR_REVIEW:
         state.registration_state = ACQUIRE_USER_REGISTRATION_STATE__INCOMPLETE;
         break;
      case ACQUIRE_USER_REGISTER__USER_IS_AUTHORISED:
         state.registration_state = ACQUIRE_USER_REGISTRATION_STATE__FINAL;
         break;
   }
   if (state.registration_state == ACQUIRE_USER_REGISTRATION_STATE__FINAL)
   {
      state.resolver = 
         (Resolver *)(new RemoteResolver(*(state.keydb),
             state.hostname, state.portno, state.operational_senderkeyname, state.operational_recipientkeyname, state.mp));
      acquireCommonSetInitialised();
   } else
   {
      state.resolver = NULL;
      acquireCommonDestroyState();
   }
   return state.registration_state;
}

/** Add an SSH host to the remote deferred login user credentials.
 * 
 * @param username_at_remotemachine The user at remote machine style
 * login, like 'rootlocalhost'.
 * 
 * @param password The password to use to SSH login to the remote
 * machine.
 * 
 * @param keyname The name of the key that is to be used as your SSH
 * key.
 * 
 * @param keypassword The password to use to encrypt the key remotely.
 * 
 * @return Zero on failure/error, non-zero encodes a remote return
 * code specified by the server. */
int acquireClientAddSSHHost(const char *username_at_remotemachine, const char *password,
   const char *keyname, const char *keypassword)
{
   if (username_at_remotemachine == NULL)
   {
      errprint("<user>@<remotemachine> string is NULL!");
      return 0;
   }
   if (strstr(username_at_remotemachine, "@") == NULL)
   {
      errprint("<user>@<remotemachine> '%s' does not contain '@' delimiter!", username_at_remotemachine);
      return 0;
   }
   if (acquireCommonStateIsIncomplete()) return 0;
   APRAutoMemoryPool pool((state.apr_iface)->getGlobalPool());
   BrokerMessage bmsg(pool.mp());
   bmsg.setRecipient(state.operational_recipientkeyname);
   bmsg.setSender(state.operational_senderkeyname);
   bmsg.addTypeHeaderNOCRLF("REGISTER_SSH_LOGIN", state.my_name);
   char *prev_copy = strdup(username_at_remotemachine);
   char *user_tok = NULL, *mach_tok = NULL;
   user_tok = apr_strtok(prev_copy, "@", &mach_tok);
   bmsg.addHeaderLineNOCRLF("ssh_machine: %s", mach_tok);
   bmsg.addHeaderLineNOCRLF("ssh_username: %s", user_tok);
   free(prev_copy);
   if (password)
      bmsg.addHeaderLineNOCRLF("ssh_password: %s", password);
   // need your password to unlock the remote key
   bmsg.addHeaderLineNOCRLF("unlock_password: %s", state.password);
   if (keyname)
      bmsg.insertAllKeysToSend(0, keyname, keypassword, state.keydb);
   if (exchangeMessage(bmsg, pool.mp(), NULL) == 1)
   {
      errprint("message exchange failed");
      return 0;
   }
   int retval = -1;
   // Got a reply... let's see what it was...
   if (bmsg.isProcessee("SUCCESS")) retval = ACQUIRE_USER_ADD_SSH__SSH_SUCCESS;
   if (bmsg.isProcessee("FINGERPRINT_ERROR")) retval = ACQUIRE_USER_ADD_SSH__FINGERPRINT_UNTRUSTED;
   if (bmsg.isProcessee("NO_ACCESS")) retval = ACQUIRE_USER_ADD_SSH__SSH_ROUTING_FAILED;
   if (bmsg.isProcessee("INVALID_AUTHENTICATION")) retval = ACQUIRE_USER_ADD_SSH__SSH_ACCESS_DENIED;
   return retval;
}
